package glashart2xmltv;

import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;

/**
 * 
 * @author Jeroen Peschier
 * @version $Id: XmltvBuilder.java 2 2013-02-09 11:54:47Z peschierj@gmail.com $
 */
public class XmltvBuilder {

	private static final String DOCTYPE_QUALIFIED_NAME = "tv";
	private static final String DOCTYPE_SYSTEM_ID = "xmltv.dtd";
	private static final String XML_VERSION = "1.0";
	private static final String DEFAULT_LANGUAGE = "nl";
	private Log log = LogFactory.getLog(XmltvBuilder.class);
	private Document document;
	
	public XmltvBuilder() {
		init();
	}
	
	private void init() {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setNamespaceAware(false);

			DocumentBuilder builder = factory.newDocumentBuilder();
			DocumentType type = builder.getDOMImplementation().createDocumentType(DOCTYPE_QUALIFIED_NAME, null, DOCTYPE_SYSTEM_ID);
			
			document = builder.getDOMImplementation().createDocument(null, DOCTYPE_QUALIFIED_NAME, type);
			document.setXmlVersion(XML_VERSION);
		} catch (Exception e) {
			log.error("Failed to initialise instance.", e);
		}
	}
	
	public void addChannel(String id, String displayName, String iconSource) {
		Element channelElement = document.createElement("channel");
		channelElement.setAttribute("id", id);
		
		Element displayNameElement = document.createElement("display-name");
		setDefaultLanuage(displayNameElement);
		displayNameElement.appendChild(document.createTextNode(displayName));
		channelElement.appendChild(displayNameElement);
		
		if (iconSource != null) {
			Element iconElement = document.createElement("icon");
			iconElement.setAttribute("src", iconSource);
			channelElement.appendChild(iconElement);
		}
		
		document.getDocumentElement().appendChild(channelElement);
	}

	public void addProgrammeEntry(String channel, ProgrammeEntry entry) {
		Element programme = document.createElement("programme");
		programme.setAttribute("start", toXmltvDate(entry.getStart()));
		programme.setAttribute("stop", toXmltvDate(entry.getEnd()));
		programme.setAttribute("channel", channel);

		if (entry.getName() != null) {
			Element title = document.createElement("title");
			setDefaultLanuage(title);
			title.appendChild(document.createTextNode(entry.getName()));
			programme.appendChild(title);
		}			
			
		if (entry.getEpisodeTitle() != null) {
			Element episode = document.createElement("episode-num");
			episode.setAttribute("system", "onscreen");
			setDefaultLanuage(episode);
			episode.appendChild(document.createTextNode(entry.getEpisodeTitle()));
			programme.appendChild(episode);
		}
		
		if (entry.getDescription() != null) {
			Element description = document.createElement("desc");
			setDefaultLanuage(description);
			description.appendChild(document.createTextNode(entry.getDescription()));
			programme.appendChild(description);
		}
				
		if (entry.getActors() != null || entry.getCommentators() != null || entry.getDirectors() != null || entry.getPresenters() != null) {
			Element credits = document.createElement("credits");
			programme.appendChild(credits);
				
			if (entry.getActors() != null) {
				for (String actorName : entry.getActors()) {
					Element actor= document.createElement("actor");
					actor.appendChild(document.createTextNode(actorName));
					credits.appendChild(actor);
				}
			}
			
			if (entry.getCommentators() != null) {
				for (String commentatorName : entry.getCommentators()) {
					Element commentator = document.createElement("commentator");
					commentator.appendChild(document.createTextNode(commentatorName));
					credits.appendChild(commentator);
				}
			}
			
			if (entry.getDirectors() != null) {
				for (String directorName : entry.getDirectors()) {
					Element director = document.createElement("director");
					director.appendChild(document.createTextNode(directorName));
					credits.appendChild(director);
				}
			}
			
			if (entry.getPresenters() != null) {
				for (String presenterName : entry.getPresenters()) {
					Element presenter = document.createElement("presenter");
					presenter.appendChild(document.createTextNode(presenterName));
					credits.appendChild(presenter);
				}
			}
		}
		
		if (entry.getGenres() != null) {
			for (String genre : entry.getGenres()) {
				Element category = document.createElement("category");
				setDefaultLanuage(category);
				category.appendChild(document.createTextNode(genre));
				programme.appendChild(category);
			}
		}
		
		document.getDocumentElement().appendChild(programme);
	}
	
	public Document getDocument() {
		return document;
	}
	
	public void serialise(OutputStream stream) throws Exception {
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer();
		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
		transformer.setOutputProperty(OutputKeys.METHOD, "xml");
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3"); // Xalan-specific, but there you go...
		transformer.transform(new DOMSource(document), new StreamResult(stream)); 
	}
	
	private void setDefaultLanuage(Element e) {
		e.setAttribute("lang", DEFAULT_LANGUAGE);
	}
	
	private String toXmltvDate(String glashartDate) {
		Long milliseconds = Long.parseLong(glashartDate) * 1000L;
		Date date = new Date(milliseconds);
		DateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss Z");
		return formatter.format(date);
	}
}